package ktt;

import java.awt.Color;
import robocode.BulletHitEvent;
import robocode.HitWallEvent;
import robocode.Robot;
import robocode.HitRobotEvent;
import robocode.Rules;
import robocode.ScannedRobotEvent;
import robocode.WinEvent;

/**
 * <code>Bubonic</code> is fast moving with decent targeting ability (much like the
 * disease).
 * 
 * 
 * @author kurtteichman
 *
 */
public class Bubonic extends Robot {
  
  /**
   * Begin global variables.
   */
  private double BATTLE_FIELD_X;
  private double BATTLE_FIELD_Y;
  private double previousDistance = 0;
  private boolean onLeftSide;
  private boolean onRightSide;
  private boolean reverseDirection = false;
  private boolean enemyStill = false;
  /**
   * Current Enemy Y coordinate.
   */
  public double enemyY;
  /**
   * Current Enemy X coordinate.
   */
  public double enemyX;
  
  /**
   * The main method for <code>Bubonic</code> functionality which calls for the robot to
   * move to one of the sides of the map and employ <code>generalMovement</code>.
   */
  public void run() {
    setAllColors(Color.BLACK);
    setBulletColor(Color.GREEN);
    this.BATTLE_FIELD_X = getBattleFieldWidth();
    this.BATTLE_FIELD_Y = getBattleFieldHeight();

    // Move to the side closest to the robot
    if (BATTLE_FIELD_X / 2 > getX()) {
      this.onLeftSide = true;
      if (getHeading() >= 90) {
        turnRight(270 - getHeading());
        ahead(BATTLE_FIELD_X - getX());
      }
      else {
        turnLeft(getHeading() + 90);
        ahead(BATTLE_FIELD_X - getX());
      }
      // Force way to side
      while (getX() > 30) { 
        ahead(BATTLE_FIELD_X - getX());
      }
      
      turnRight(90);
      turnGunRight(180);
    } 
    else {
      this.onRightSide = true;
      if ( (getHeading() >= 90) && (getHeading() < 270)) {
        turnLeft(getHeading() - 90);
        ahead(BATTLE_FIELD_X - getX());
      }
      else {
        if ( (getHeading() < 90 )) {
          turnRight(90 - getHeading());
        } 
        else {
          turnRight(360 - getHeading() + 90);
        }
        ahead(BATTLE_FIELD_X - getX());
      }
      // Force way to side
      while (getX() < (BATTLE_FIELD_X - 30)) {
        ahead(BATTLE_FIELD_X - getX());
      }
      
      turnLeft(90);
      turnGunLeft(180);
    }
    
    // Allow for radar to move independently of gun
    setAdjustRadarForGunTurn(true);
    while (true) {
      // Sweep towards top side of battlefield
      if (onLeftSide) {
        turnRadarLeft(getRadarHeading());
      }
      else if (onRightSide) {
        turnRadarRight(359 - getRadarHeading());
      }
      out.println("Radar1: " + getRadarHeading());
      generalMovement();
      // Sweep towards bottom side of battlefield
      if (onLeftSide) {
        turnRadarRight(180 - getRadarHeading());
      }
      else if (onRightSide) {
        turnRadarLeft(getRadarHeading() - 180);
      }
      out.println("Radar2: " + getRadarHeading());
      generalMovement();
    }
  }
  
  /**
   * Method determines action upon handling a <code>ScannedRobotEvent</code> as well
   * as simple linear targeting, although the math isn't so trivial. For a more detailed
   * explanation of this algorithm, please see <url>http://robowiki.net/wiki/Linear_Targeting<url>.
   * The original code had to be modified as it was originally designed for an advanced robot.
   * Side note : the robot gun moves independently of the radar. Also, if one doesn't understand
   * the discriminant usage to solving quadratic equations, see:
   * <url>http://www.analyzemath.com/Equations/Quadratic-1.html<url>.
   * @param e (<code>ScannedRobotEvent</code>)
   */
  public void onScannedRobot(ScannedRobotEvent e) {
    // if the enemy is standing still from the last scan, drill them with a hard shot
    setGunColor(Color.GREEN);
    if (this.previousDistance == e.getDistance()) {
      this.enemyStill = true;
      fire(3);
      scan();
    }
    else {
      enemyStill = false;
    }
    this.previousDistance = e.getDistance();
    
    final double ROBOT_WIDTH = 16;
    final double ROBOT_HEIGHT = 16;
    // Incoming Trig ...
    final double eAbsBearing = Math.toRadians(getHeading()) + e.getBearingRadians();
    final double currentX = getX(), currentY = getY(),
        bulletVelocity = Rules.getBulletSpeed(getBulletPower(e));
    enemyX = currentX + e.getDistance() * Math.sin(eAbsBearing);
    enemyY = currentY + e.getDistance() * Math.cos(eAbsBearing);
    final double enemyVelocity = e.getVelocity();
    final double enemyHeadingRadians = e.getHeadingRadians();
    // These constants make calculating the quadratic coefficients below easier
    final double A = (enemyX - currentX) / bulletVelocity;
    final double B = enemyVelocity / bulletVelocity * Math.sin(enemyHeadingRadians);
    final double C = (enemyY - currentY) / bulletVelocity;
    final double D = enemyVelocity / bulletVelocity * Math.cos(enemyHeadingRadians);
    //Quadratic coefficients: a*(1/t)^2 + b*(1/t) + c = 0 ie: Quadratic formula
    final double a = A * A + C * C;
    final double b = 2 * (A * B + C * D);
    final double c = (B * B + D * D - 1);
    final double discrim = b * b - 4 * a * c; 
    if (discrim >= 0) {
      // Reciprocal of quadratic formula
      final double time1 = 2 * a / (-b - Math.sqrt(discrim)); // - sqrt(b^2 - 4ac)
      final double time2 = 2 * a / (-b + Math.sqrt(discrim)); // + sqrt(b^2 - 4ac)
      final double timeCalculation = Math.min(time1, time2) >= 0 
                                        ? Math.min(time1, time2) : Math.max(time1, time2);
      // Assume enemy stops at walls
      final double limitX = limit(enemyX + enemyVelocity * timeCalculation
                                  * Math.sin(enemyHeadingRadians), ROBOT_WIDTH / 2,
                                  BATTLE_FIELD_X - ROBOT_WIDTH / 2);
      final double limitY = limit(enemyY + enemyVelocity * timeCalculation
                                  * Math.cos(enemyHeadingRadians), ROBOT_HEIGHT / 2,
                                  BATTLE_FIELD_Y - ROBOT_HEIGHT / 2);
      turnGunRight(Math.toDegrees(robocode.util.Utils.normalRelativeAngle(
                   Math.atan2(limitX - currentX, limitY - currentY)
                   - Math.toRadians(getGunHeading()))));
      fire(getBulletPower(e));
    }

    scan();
  }

  /**
   * Method called by <code>onScannedRobot</code> targeting algorithm to determine
   * an absolute maximum a robot can travel on the battlefield.
   * 
   * @param value A value
   * @param min The min value
   * @param max The max value
   * @return minMax The relative minMax
   */
  public double limit(double value, double min, double max) {
    return Math.min(max, Math.max(min, value));
  }
  
  /**
   * Method implements simple bullet dodging that somewhat works. The pseudo
   * dodging occurs on a <code>BulletHitEvent</code> trigger.
   * "Are you saying that I can dodge bullets?" - Neo
   * 
   * @param e BulletHitEvent
   */
  public void onBulletHit(BulletHitEvent e) {
    setGunColor(Color.BLACK);
    if (!enemyStill) {
      if (this.reverseDirection) {  
        ahead(25);
      }
      else {
        back(25);
      }
    }
  }
  
  /**
   * Method called by <code>onScannedRobot</code> to determine power of shot.
   * 
   * @param e ScannedRobotEvent
   * @return 1, 2, or 3 based on the distance to the target
   */
  public int getBulletPower(ScannedRobotEvent e) {
    if (e.getDistance() < 300) {
      return 3;
    }
    else if (e.getDistance() < 600) {
      return 2;
    }
    else {
      return 1;
    }
  }
  
  /**
   * Method causes the switching of the <code>reverseDirection</code> boolean to affect
   * the method <code>generalMovement</code> which controls <code>Bubonic</code>'s movement
   * in the Y direction.
   * 
   * @param e HitWallEvent
   */
  public void onHitWall(HitWallEvent e) {
    if (this.reverseDirection) {
      reverseDirection = false;
    }
    else {
      reverseDirection = true;
    }
  }
  
  /**
   * Method causes, depending on how <code>reverseDirection</code> boolean is set,
   * <code>Bubonic</code> to move forward/backward in increments determined by
   * (absolute difference between the its current Y coordinate and the enemy's
   * previous Y coordinate + 75) units/(set of actions).
   */
  public void generalMovement() {
    double yDifference = getY() - enemyY;
    yDifference = Math.abs(yDifference);
    if (reverseDirection) {
      back(yDifference + 85);
    }
    else {
      ahead(yDifference + 85);
    }
  }
  
  /**
   * Method causes <code>Bubonic</code> to reverse if hit from the front
   * or to move forward if hit from the back.
   * 
   * @param e HitRobotEvent
   */
  public void onHitRobot(HitRobotEvent e) {
    // If he's in front of us, set back up a bit.
    if (e.getBearing() > -90 && e.getBearing() < 90) {
      back(110);
    } // else he's in back of us, so set ahead a bit.
    else {
      ahead(110);
    }
  }
  
  /**
   * Method implements the functionality of the robot doing a small
   * dance upon winning a battle.
   * 
   * @param event (not currently used)
   */
  public void onWin(WinEvent event) {
    while (true) { // seizure dance
      turnRight(30);
      turnLeft(30);
    }
  }
} // end of Bubonic class


